/* The MIT License (MIT)

	Copyright (c) 2013 Schpitt <schpitt@gmail.com>
	https://code.google.com/p/updator/

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE. */

#include "Updator.h"

#include "IUpdateListener.h"
#include "Downloader.h"
#include "Task.h"
#include "TaskAddIfNotExist.h"
#include "TaskAddOrReplace.h"
#include "TaskDeleteIfExist.h"
#include "TaskDeleteIfHash.h"
#include "TaskUpdate.h"
#include "TaskQueue.h"
#include "Logger.h"
#include "Utils.h"

#include <SFML/System.hpp>

#include <string>
#include <vector>

using namespace std;

namespace Updator 
{

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

void thread_check_launcher(void* UserData)
{
	(static_cast<Updator*>(UserData))->checkTasks();
}
void thread_execute_launcher(void* UserData)
{
	(static_cast<Updator*>(UserData))->executeTasks();
}
void thread_postexecute_launcher(void* UserData)
{
	(static_cast<Updator*>(UserData))->postExecuteTasks();
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Updator::Updator( const string& p_host, 
				  const string& p_folder, 
				  const string& p_hash_filename, 
				  unsigned char p_check_thread_number, 
				  unsigned int p_check_buffer_size, 
				  unsigned char p_postexecute_thread_number, 
				  unsigned int p_postexecute_buffer_size ) :
		stopped(false),
		hash_filename(p_hash_filename),
		listener(0),
		check_thread_number(p_check_thread_number),
		check_buffer_size(p_check_buffer_size),
		postexecute_thread_number(p_postexecute_thread_number),
		postexecute_buffer_size(p_postexecute_buffer_size),
		check_thread_running(p_check_thread_number),
		execute_thread_running(true)
{ 
	downloader = new Downloader(p_host, p_folder);
	downloader->setListener(this);

	tasks_to_check = new TaskQueue();
	tasks_to_execute = new TaskQueue();
	tasks_to_postexecute = new TaskQueue();
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Updator::~Updator()
{
	delete downloader;
	delete tasks_to_check;
	delete tasks_to_execute;
	delete tasks_to_postexecute;
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

void Updator::setListener(IUpdateListener* p_listener)
{
	listener = p_listener;
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

void Updator::stopUpdate()
{
	stopped = true;
	downloader->disconnect();
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

void Updator::startUpdate()
{	
	// connect
	if( !downloader->connect() ) {
		if(listener!=0) listener->connected( false );
		LOG("[hupd] connect failed, stop update");
		return;
	}
	if(listener!=0) listener->connected( true );

	// get file info
	LOG("[hupd] get file info");
	string fileinfo;
	if( !getFileInfo( fileinfo ) ) {
		LOG("[hupd] get file info failed, stop update");
		return;
	}

	// prehandle file info
	LOG("[hupd] prehandle file info");
	if( !prehandleFileInfo( fileinfo ) ) {
		LOG("[hupd] prehandle file info failed, stop update");
		return;
	}


	sf::Thread* executer = 0;
	vector<sf::Thread*> checkers;
	vector<sf::Thread*> postexecuters;

	if(listener!=0) listener->addToCheck();
		
	startCheckers(checkers);
	startExecuters(executer);
	startPostExecuters(postexecuters);

	waitForAll(checkers, executer, postexecuters);
		
	// delete check thread (if multithread)
	if( check_thread_number > 0 ) {
		for(unsigned char i=0; i<check_thread_number; ++i)
			delete checkers[i];
		checkers.clear();
	}
	// delete postexecuter thread (if multithread)
	if( postexecute_thread_number > 0 ) {
		for(unsigned char i=0; i<postexecute_thread_number; ++i)
			delete postexecuters[i];
		postexecuters.clear();
	}

	delete executer;

	if(listener!=0) listener->updateEnd( true );
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

void Updator::startCheckers(vector<sf::Thread*>& p_threads)
{
	// check tasks (hash, exist, size, ...)
	if( check_thread_number == 0 ) {
		LOG("[hupd] check tasks (same thread)");
		checkTasks();
	} else {
		for(unsigned char i=0; i<check_thread_number; ++i) {
			LOG("[hupd] launch thread checker" << i);
			sf::Thread* checker = new sf::Thread(thread_check_launcher, this);
			p_threads.push_back(checker);
			checker->launch();
		}
	} 
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

void Updator::startExecuters(sf::Thread* p_thread)
{
	// execute tasks
	if( postexecute_thread_number == 0 ) {
		LOG("[hupd] execute tasks (same thread)");
		executeTasks();
	} else {
		LOG("[hupd] launch thread executer");
		p_thread = new sf::Thread(thread_execute_launcher, this);
		p_thread->launch();
	}
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

void Updator::startPostExecuters(vector<sf::Thread*>& p_threads)
{	
	// post execute tasks (decompress, ...)
	if( postexecute_thread_number == 0 ) {
		LOG("[hupd] post execute tasks (same thread)");
		postExecuteTasks();
	} else {
		for(unsigned char i=0; i<postexecute_thread_number; ++i) {
			LOG("[hupd] launch thread post executer" << i);
			sf::Thread* postexecuter = new sf::Thread(thread_postexecute_launcher, this);
			p_threads.push_back(postexecuter);
			postexecuter->launch();
		}
	} 
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

void Updator::waitForAll(std::vector<sf::Thread*>& p_checkers, sf::Thread* p_executer, std::vector<sf::Thread*>& p_post_executor)
{
	for(auto it = p_checkers.begin(); it != p_checkers.end(); ++it)
		(*it)->wait();

	if(p_executer) 
		p_executer->wait();

	for(auto it = p_post_executor.begin(); it != p_post_executor.end(); ++it)
		(*it)->wait();

	LOG("[hupd] all have been waited" );
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

bool Updator::getFileInfo( string& p_hashfile ) 
{
	// download hashfile on the http server
	if( !downloader->getData(hash_filename, p_hashfile) ) {
		LOG("[hupd] can't get hash file (" << hash_filename.c_str() << ")" );
		return false;
	}

	return true;
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

bool Updator::prehandleFileInfo( string& p_fileinfo ) 
{
	vector<string> tokens;
	Utils::split(p_fileinfo, '\n', tokens);

	// for each line
	for(vector<string>::size_type i=0; i<tokens.size(); ++i) 
	{		
		replace( tokens[i].begin(), tokens[i].end(), '\\', '/' );
		replace( tokens[i].begin(), tokens[i].end(), '\r', '\0' );
		replace( tokens[i].begin(), tokens[i].end(), '\n', '\0' );
			
		// create new task depends on the first character
		switch(tokens[i][0]) {
			case ADD_IF_NOT_EXIST:
				//LOG("[hupd] new task to check ADD_IF_NOT_EXIST : " << tokens[i].c_str());
				tasks_to_check->pushTask( new TaskAddIfNotExist( tokens[i], downloader ) );
				break;
			case ADD_OR_REPLACE:
				//LOG("[hupd] new task to check ADD_OR_REPLACE : " << tokens[i].c_str());
				tasks_to_check->pushTask( new TaskAddOrReplace( tokens[i], downloader ) );
				break;
			case UPDATE_IF_EXIST :
				//LOG("[hupd] new task to check UPDATE_IF_EXIST : " << tokens[i].c_str());
				tasks_to_check->pushTask( new TaskUpdate( tokens[i], downloader ) );
				break;
			case DELETE_IF_HASH_OK:
				//LOG("[hupd] new task to check DELETE_IF_HASH_OK : " << tokens[i].c_str());
				tasks_to_check->pushTask( new TaskDeleteIfHash( tokens[i] ) );
				break;
			case DELETE_IF_EXIST:
				//LOG("[hupd] new task to check DELETE_IF_EXIST : " << tokens[i].c_str());
				tasks_to_check->pushTask( new TaskDeleteIfExist( tokens[i] ) );
				break;
			default:
				LOG("[hupd] not implemented [" << tokens[i].c_str() << "]");
		}
	}

	return true;
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

void Updator::downloadProgress( int p_size ) 
{
	if(listener!=0) listener->executeProgress( p_size );
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

void Updator::checkTasks()
{
	Task* to_check;
	// the buffer is allocated here to avoid realloc for each file
	char* buffer = (char*)malloc(check_buffer_size);
	
	while( tasks_to_check->popTask( &to_check ) ) {
		if( to_check->check( buffer, check_buffer_size ) ) {

			LOG("[hupd] add task to execute " << to_check->getType() << "-" << to_check->getPath());
			
			if(listener!=0) listener->addToExecute( to_check->getSize() );

			tasks_to_execute->pushTask( to_check );
		} else
			delete to_check;

		if(listener!=0) listener->checkProgress();
	}

	if(check_thread_running)
		--check_thread_running;

	free(buffer);
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

void Updator::executeTasks() 
{
	do  {
		sf::sleep(sf::milliseconds(1));

		Task* to_execute;

		while( tasks_to_execute->popTask( &to_execute ) ) {
			LOG("[hupd] exec filename [" << to_execute->getPath().c_str() << "]");
			if(listener!=0) listener->executeBegin( to_execute->getPath().c_str(), to_execute->getSize() );
			if( to_execute->execute() ) {
				if(listener!=0) listener->executeEnd(true);
			    if(listener!=0) listener->addToPostExecute();

				tasks_to_postexecute->pushTask(to_execute);

			} else {
				LOG("[hupd] can't execute");
				if(listener!=0) listener->executeEnd(false);
				delete to_execute;
			}
		
		}
	} while( check_thread_running );

	execute_thread_running = false;

	LOG("[hupd] executer stop");
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

void Updator::postExecuteTasks() 
{
	do  {
		sf::sleep(sf::milliseconds(1));

		Task* to_postexecute;

		while( tasks_to_postexecute->popTask( &to_postexecute ) ) {
			LOG("[hupd] post exec filename [" << to_postexecute->getPath().c_str() << "]");
			
			to_postexecute->postExecute();

			if(listener!=0) listener->postExecuteProgress();

			delete to_postexecute;
		}
	} while( execute_thread_running );

	LOG("[hupd] post executer stop");
}

//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

} // namespace Updator
